package server

import (
	"bufio"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"
	"sync"
)

type Server interface {
	Start() error

	Shutdown() error
}

type Connection interface {
	Handle() error

	Close() error
}

type TCPServer struct {
	Server

	Addr     *net.TCPAddr
	listener *net.TCPListener
}

func (server *TCPServer) Start() error {
	listener, err := net.ListenTCP("tcp", server.Addr)

	if err != nil {
		return err
	}

	server.listener = listener

	go server.accept()

	return nil
}

func (server *TCPServer) accept() {
	for {
		conn, err := server.listener.AcceptTCP()
		if err != nil {
			log.Println("error occurred while accepting connection", err.Error())
			continue
		}

		connection := TCPConnection{
			server: server,
			conn:   conn,
		}

		go func() {
			err := connection.Handle()
			if err != nil {
				log.Println("error occurred while handling connection", err.Error())
			}
		}()
	}
}

func (server *TCPServer) Shutdown() error {
	if server.listener == nil {
		return nil
	}

	err := server.listener.Close()
	if err != nil {
		server.listener = nil
	}
	return err
}

type TCPConnection struct {
	Connection

	server *TCPServer
	conn   *net.TCPConn
}

func (connection *TCPConnection) Handle() error {
	defer func() {
		_ = connection.Close()
	}()

	reader := bufio.NewReader(connection.conn)
	request, err := http.ReadRequest(reader)
	if err != nil {
		return err
	}

	method := strings.ToUpper(request.Method)
	if method != "CONNECT" {
		return errors.New(fmt.Sprintf("expecting request method 'CONNECT' but got '%s', request: %p", method, request))
	}

	conn, err := net.Dial("tcp", request.RequestURI)
	if err != nil {
		return err
	}

	_, err = connection.conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
	if err != nil {
		return err
	}

	waitGroup := sync.WaitGroup{}
	waitGroup.Add(1)
	go func() {
		err := doProxyReading(connection, conn)
		if err != nil {
			//log.Println("error occurred while proxying reading", err.Error())
		}
		waitGroup.Done()
	}()

	waitGroup.Add(1)
	go func() {
		err := doProxyWriting(connection, conn)
		if err != nil {
			//log.Println("error occurred while proxying writing", err.Error())
		}
		waitGroup.Done()
	}()

	waitGroup.Wait()

	return nil
}

func doProxyReading(connection *TCPConnection, conn net.Conn) error {
	buf := make([]byte, 1024*8)
	for {
		l, err := conn.Read(buf)
		if err != nil {
			return err
		}
		_, err = connection.conn.Write(buf[:l])
		if err != nil {
			return err
		}
	}
}

func doProxyWriting(connection *TCPConnection, conn net.Conn) error {
	buf := make([]byte, 1024*8)
	for {
		l, err := connection.conn.Read(buf)
		if err != nil {
			return err
		}
		_, err = conn.Write(buf[:l])
		if err != nil {
			return err
		}
	}
}

func (connection *TCPConnection) Close() error {
	if connection.conn == nil {
		return nil
	}

	err := connection.conn.Close()

	if err == nil {
		connection.conn = nil
	}

	return err
}
